/*
 * Galaxium Messenger
 * Copyright (C) 2008 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Runtime.InteropServices;

using Cairo;

using Anculus.Core;

using Galaxium.Core;
using Galaxium.Gui;

namespace Galaxium.GStreamer
{
	public class GStreamerVideoPlayer : IVideoPlayer
	{
		public event EventHandler Error;
		public event EventHandler<ByteArrayEventArgs> NewFrame;
		
		IntPtr _native;
		HandleRef _handle;
		
		GStreamerWebcamDevice _device = null;
		VideoCodec _codec = VideoCodec.Unknown;
		
		ImageSurface _surface;
		
		int _width, _height;
		
		GCHandle _errorHandle;
		GCHandle _newFrameHandle;
		GCHandle _newSizeHandle;
		
		bool _disposed;
		
		internal int _references = 1;
		
		public Surface Surface
		{
			get { return _surface; }
		}
		
		public int Width
		{
			get { return _width; }
		}
		
		public int Height
		{
			get { return _height; }
		}
		
		public VideoCodec Codec
		{
			get { return _codec; }
			set
			{
				if (_codec == value)
					return;
				
				_codec = value;
				
				if (_device != null)
				{
					string encoderStr = GetEncoderString ();
					IntPtr strPtr = GLib.Marshaller.StringToPtrGStrdup (encoderStr);
					GStreamerInterop.gstreamer_video_set_encoder (_handle, strPtr);
					GLib.Marshaller.Free (strPtr);
				}
				else
				{
					string decoderStr = GetDecoderString ();
					IntPtr strPtr = GLib.Marshaller.StringToPtrGStrdup (decoderStr);
					GStreamerInterop.gstreamer_video_set_decoder (_handle, strPtr);
					GLib.Marshaller.Free (strPtr);
				}
			}
		}
		
		public GStreamerWebcamDevice Device
		{
			get { return _device; }
		}
		
		public unsafe GStreamerVideoPlayer (GStreamerWebcamDevice device, GStreamerVideoFormat format)
		{
			if (device != null)
			{
				_device = device;
				
				Log.Debug ("Initializing local video player");
				
				NativeGalaxiumWebcamDevice nativeDevice = device.Native;
				NativeGalaxiumVideoFormat nativeFormat = format.Native;
				NativeGalaxiumFramerate framerate = format.GetHighestFramerate ();
				
				Log.Debug ("Using device '{0}'", device.Name);
				Log.Debug ("Using video format {0} {1}x{2} {3}Hz", format.MimeType, nativeFormat.width, nativeFormat.height, (float)framerate.numerator / framerate.denominator);
				
				Init (GStreamerInterop.gstreamer_video_new_local (ref nativeDevice, ref nativeFormat, ref framerate));
			}
			else
			{
				Log.Debug ("Initializing remote video player");
				
				Init (GStreamerInterop.gstreamer_video_new_remote ());
			}
			
			GStreamerVideoBackend._activePlayers.Add (this);
		}
		
		~GStreamerVideoPlayer ()
		{
			Dispose (true);
		}
		
		void Init (IntPtr native)
		{
			_native = native;
			
			ThrowUtility.ThrowIfTrue ("Native pointer is null", _native == IntPtr.Zero);
			
			_handle = new HandleRef (this, _native);
			
			GStreamerErrorDelegate errorCallback = new GStreamerErrorDelegate (CallbackError);
			GStreamerNewFrameDelegate newFrameCallback = new GStreamerNewFrameDelegate (CallbackNewFrame);
			GStreamerNewSizeDelegate newSizeCallback = new GStreamerNewSizeDelegate (CallbackNewSize);
			
			_errorHandle = GCHandle.Alloc (errorCallback, GCHandleType.Pinned);
			_newFrameHandle = GCHandle.Alloc (newFrameCallback, GCHandleType.Pinned);
			_newSizeHandle = GCHandle.Alloc (newSizeCallback, GCHandleType.Pinned);
			
			GStreamerInterop.gstreamer_video_set_error_callback (_handle, errorCallback);
			GStreamerInterop.gstreamer_video_set_newframe_callback (_handle, newFrameCallback);
			GStreamerInterop.gstreamer_video_set_newsize_callback (_handle, newSizeCallback);
		}
		
		public void Dispose ()
		{
			Dispose (false);
			
			GC.SuppressFinalize (this);
		}
		
		void Dispose (bool destroyed)
		{
			//Log.Debug ("Dispose");
			
			if ((!_disposed) && (_handle.Handle != IntPtr.Zero))
			{
				_disposed = true;
				
				GStreamerInterop.gstreamer_video_free (_handle);
				
				_errorHandle.Free ();
				_newFrameHandle.Free ();
				_newSizeHandle.Free ();
				
				if (_surface != null)
					_surface.Destroy ();
			}
			
			if (GStreamerVideoBackend._activePlayers.Contains (this))
				GStreamerVideoBackend._activePlayers.Remove (this);
		}
		
		public void Close ()
		{
			_references--;
			
			if (_references == 0)
				Dispose ();
		}
		
		public void SetScale (int width, int height)
		{
			GStreamerInterop.gstreamer_video_set_scale (_handle, width, height);
		}
		
		public void Play ()
		{
			if (_disposed)
				return;
			
			GStreamerInterop.gstreamer_video_play (_handle);
		}
		
		public void Stop ()
		{
			if (_disposed)
				return;
			
			GStreamerInterop.gstreamer_video_stop (_handle);
		}
		
		public void WriteData (byte[] data)
		{
			if (_disposed)
				return;
			
			GStreamerInterop.gstreamer_video_write (_handle, data, data.Length);
		}
		
		string GetEncoderString ()
		{
			if (_codec == VideoCodec.Mimic)
				return "mimenc";
			
			return string.Empty;
		}
		
		string GetDecoderString ()
		{
			if (_codec == VideoCodec.Mimic)
				return "mimdec";
			
			return string.Empty;
		}
		
#region Callbacks
		void CallbackNewSize (IntPtr native, int width, int height)
		{
			if (_disposed)
				return;
			
			_width = width;
			_height = height;
			
			if (_surface != null)
				_surface.Destroy ();
			
			_surface = new ImageSurface (Format.ARGB32, _width, _height);
			
			GStreamerInterop.gstreamer_video_set_surface (_handle, _surface.Handle);
		}
		
		void CallbackNewFrame (IntPtr native)
		{
			if (_disposed)
				return;
			
			// Be very careful we don't throw any unhandled exceptions in these callback methods
			
			try
			{
				if (NewFrame != null)
					NewFrame (this, new ByteArrayEventArgs (new byte[0]));
			}
			catch (Exception ex)
			{
				Log.Error (ex, "Error in new frame handler");
			}
		}
		
		void CallbackError (IntPtr native, uint domain, int code, IntPtr error, IntPtr debug)
		{
			if (_disposed)
				return;
			
			try
			{
				Stop ();
				
				string errorMsg = error == IntPtr.Zero ? "Unknown Error" : GLib.Marshaller.Utf8PtrToString (error);
				
				Log.Error (errorMsg);
				
				if (Error != null)
					Error (this, EventArgs.Empty);
			}
			catch (Exception ex)
			{
				Log.Error (ex, "Error in error handler...");
			}
		}
#endregion
	}
}
